using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Practices.CompositeWeb.Interfaces;
using System.Reflection;
using Microsoft.Practices.CompositeWeb;
using WCSFContrib.Extensions.CompositeWeb.Configuration;
using WCSFContrib.Extensions.CompositeWeb.Interfaces;
using System.Globalization;
using Microsoft.Practices.CompositeWeb.Utility;

namespace WCSFContrib.Extensions.CompositeWeb.Services
{
    public class ModuleLoaderService : IModuleLoaderService
    {
        private Dictionary<string, IModuleInitializer> _modules = new Dictionary<string, IModuleInitializer>();

        /// <summary>
        /// Initializes a new instance of <see cref="ModuleLoaderService"/>.
        /// </summary>
        public ModuleLoaderService()
        {
        }

        /// <summary>
        /// Loads the specified modules into the given container.
        /// </summary>
        /// <param name="compositionContainer">The container into wich create and add the module published services.</param>
        /// <param name="modules">The list of modules to load.</param>
        /// <remarks>A <see cref="CompositionContainer"/> is created for every module in the list and added to the 
        /// <paramref name="compositionContainer"/>. The Load() method is called on every module that
        /// exposes a <see cref="IModuleInitializer"/>.</remarks>
        public void Load(ICompositionContainer compositionContainer, params IModuleInfo[] modules)
        {
            Guard.ArgumentNotNull(compositionContainer, "compositionContainer");
            Guard.ArgumentNotNull(modules, "modules");
            if (!typeof(CompositionContainer).IsAssignableFrom(compositionContainer.GetType()))
                throw new ArgumentException(String.Format(CultureInfo.CurrentCulture, Properties.Resources.ContainerIsNotCompositionContainer, typeof(CompositionContainer).FullName), "compositionContainer");

            foreach (IModuleInfo moduleInfo in modules)
            {
                Assembly moduleAssembly = Assembly.Load(moduleInfo.AssemblyName);

                CompositionContainer container = String.IsNullOrEmpty(moduleInfo.VirtualPath) ? (CompositionContainer)compositionContainer :
                    compositionContainer.Containers.AddNew<CompositionContainer>(moduleInfo.Name);

                foreach (Type t in moduleAssembly.GetTypes())
                {
                    if (typeof(IModuleInitializer).IsAssignableFrom(t))
                    {
                        LoadServices(container, moduleInfo);
                        
                        IModuleInitializer init = (IModuleInitializer)container.BuildNewItem(t);
                        _modules.Add(moduleInfo.Name, init);
                        init.Load(container);
                    }
                }
            }
        }

        private static void LoadServices(CompositionContainer container, IModuleInfo moduleInfo)
        {
            IServiceLoader serviceLoader = container.Services.Get<IServiceLoader>();

            Guard.ArgumentNotNull(serviceLoader, "serviceLoader");

            CustomDependantModuleInfo dependantModuleInfo = moduleInfo as CustomDependantModuleInfo;

            if (dependantModuleInfo != null && dependantModuleInfo.Services != null)
            {
                serviceLoader.Load(container, dependantModuleInfo.Services);
            }
        }

        /// <summary>
        /// Gets the <see cref="IModuleInitializer"/> exposed by the requested module.
        /// </summary>
        /// <param name="moduleName">The name of the module to get its initializer.</param>
        /// <returns>The exposed <see cref="IModuleInitializer"/> if found; otherwise, <see langword="null"/>.</returns>
        public IModuleInitializer FindInitializer(string moduleName)
        {
            Guard.ArgumentNotNullOrEmptyString(moduleName, "moduleName");

            if (_modules.ContainsKey(moduleName)) return _modules[moduleName];
            return null;
        }
    }
}